using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Common.Shared.Xml.Wddx;
using System.Xml;
using Common.Shared.Reflection;
using Common.Shared.Design;
using System.Reflection;
using System.Collections;

namespace Common.Shared.Xml.Serialization {
	public class WddxSerializer {

		#region Serialize
		/// <summary>
		/// Serializes the specified object to the specified stream.
		/// </summary>
		/// <param name="stream">The stream.</param>
		/// <param name="o">The object to serialize.</param>
		/// <param name="formated">if set to <c>true</c> the xml will be indented and formated.</param>
		public void Serialize ( Stream stream, WddxObject o, bool formated ) {
			XmlWriterSettings settings = new XmlWriterSettings ( );
			settings.Indent = formated;
			settings.NewLineChars = formated ? Environment.NewLine : string.Empty;
			using ( XmlWriter w = XmlTextWriter.Create ( stream, settings ) ) {
				w.WriteStartDocument ( );
				string localName = GetWddxRootElementName ( o );
				InternalSerializeElement ( w, o, localName );
				w.WriteEndDocument ( );
			}
		}

		/// <summary>
		/// Serializes the specified object to the specified stream.
		/// </summary>
		/// <param name="writer">The writer.</param>
		/// <param name="o">The o.</param>
		public void Serialize ( XmlWriter writer, WddxObject o ) {
			using ( writer ) {
				writer.WriteStartDocument ( );
				string localName = GetWddxRootElementName ( o );
				InternalSerializeElement ( writer, o, localName );
				writer.WriteEndDocument ( );
			}
		}

		/// <summary>
		/// Serializes the specified object to the specified stream.
		/// </summary>
		/// <param name="stream">The stream.</param>
		/// <param name="o">The object to serialize.</param>
		public void Serialize ( Stream stream, WddxObject o ) {
			Serialize ( stream, o, false );
		}

		/// <summary>
		/// Serializes the specified object to the specified stream.
		/// </summary>
		/// <param name="file">The file.</param>
		/// <param name="o">The object to serialize.</param>
		public void Serialize ( FileInfo file, WddxObject o ) {
			using ( FileStream fs = new FileStream ( file.FullName, FileMode.Create, FileAccess.Write ) ) {
				Serialize ( fs, o );
			}
		}

		/// <summary>
		/// Serializes the specified object to the specified stream.
		/// </summary>
		/// <param name="file">The file.</param>
		/// <param name="o">The object to serialize.</param>
		/// <param name="formated">if set to <c>true</c> the xml will be indented and formated.</param>
		public void Serialize ( FileInfo file, WddxObject o, bool formated ) {
			using ( FileStream fs = new FileStream ( file.FullName, FileMode.Create, FileAccess.Write ) ) {
				Serialize ( fs, o, formated );
			}
		}
		#endregion

		#region Deserialize
		public WddxPacket Deserialize ( Stream stream ) {
			WddxPacket packet = null;
			using ( XmlTextReader reader = new XmlTextReader ( stream ) ) {
				reader.WhitespaceHandling = WhitespaceHandling.None;
				packet = Deserialize ( reader );
			}
			return packet;
		}

		public WddxPacket Deserialize ( TextReader textReader ) {
			WddxPacket packet = null;
			using ( XmlTextReader reader = new XmlTextReader ( textReader ) ) {
				packet = Deserialize ( reader );
			}
			return packet;
		}

		public WddxPacket Deserialize ( FileInfo file ) {
			WddxPacket packet = null;
			using ( FileStream fs = new FileStream ( file.FullName, FileMode.Open, FileAccess.Read ) ) {
				packet = Deserialize ( fs );
			}
			return packet;
		}

		public WddxPacket Deserialize ( XmlReader xmlReader ) {
			WddxPacket packet = InternalDeserializeElement ( xmlReader as XmlTextReader ) as WddxPacket;
			return packet;
		}

		public T Deserialize<T> ( XmlReader xmlReader ) {
			WddxPacket packet = Deserialize ( xmlReader );
			// are we done?
			/*if ( typeof ( T ) == typeof ( WddxPacket ) ) {
				return packet;
			}*/

			if ( packet != null && packet.Data != null && packet.Data.Value != null ) {
				WddxDataTypeObject obj = packet.Data.Value;
				// get the type of the value object
				if ( !string.IsNullOrEmpty ( obj.Type ) && TypeUtils.IsValidType ( obj.Type ) ) {
					// todo: convert based on the data type
					return default ( T );
				}
			}

			return default ( T );
		}

		public T Deserialize<T> ( Stream stream ) {
			T result = default ( T );
			using ( XmlTextReader reader = new XmlTextReader ( stream ) ) {
				result = Deserialize<T> ( reader );
			}
			return result;
		}

		public T Deserialize<T> ( TextReader textReader ) {
			T result = default ( T );
			using ( XmlTextReader reader = new XmlTextReader ( textReader ) ) {
				result = Deserialize<T> ( reader );
			}
			return result;
		}

		public T Deserialize<T> ( FileInfo file ) {
			T result = default ( T );
			using ( FileStream fs = new FileStream ( file.FullName, FileMode.Open, FileAccess.Read ) ) {
				result = Deserialize<T> ( fs );
			}
			return result;
		}
		#endregion

		#region Public Methods
		public bool CanDeserialize ( XmlReader reader ) {
			bool result = false;
			using ( reader ) {
				if ( reader.MoveToElement ( ) ) {
					result = string.Compare ( reader.LocalName, "wddxPacket" ) == 0 && string.Compare ( reader.GetAttribute ( "version" ), "1.0" ) == 0;
				}
			}
			return result;
		}
		#endregion

		#region Private Serialize Methods
		private void InternalSerializeElement ( XmlWriter w, WddxObject o, string localName ) {
			if ( string.IsNullOrEmpty ( localName ) ) {
				localName = GetWddxRootElementName ( o );
			}
			if ( o == null ) {
				return;
			}
			Console.WriteLine ( "Serializing object: {0}: {1}", o.GetType ( ).Name, localName );
			w.WriteStartElement ( localName );
			// get attributes
			foreach ( MemberInfo mi in MemberUtils.GetMembersWithAttribute<WddxAttributeAttribute> ( o.GetType ( ) ) ) {
				object val = MemberUtils.GetMemberInfoValue<object> ( o, mi );
				if ( val == null ) {
					continue;
				}
				if ( AttributeUtils.IsWddxAttribute ( mi ) ) {
					string attrName = GetWddxAttributeName ( mi );
					string attrValue = MemberUtils.GetMemberInfoStringValue ( o, mi );
					if ( !string.IsNullOrEmpty ( attrValue ) && !string.IsNullOrEmpty ( attrName ) ) {
						w.WriteAttributeString ( attrName, attrValue );
					}
				}
			}
			//get elements
			List<MemberInfo> miItems = MemberUtils.GetMembersWithAttribute<WddxElementAttribute> ( o.GetType ( ) );
			miItems.AddRange ( MemberUtils.GetMembersWithAttribute<WddxTextAttribute> ( o.GetType ( ) ) );

			foreach ( MemberInfo mi in miItems ) {
				object val = MemberUtils.GetMemberInfoValue<object> ( o, mi );
				if ( val == null || AttributeUtils.IsWddxIgnore ( mi ) ) {
					continue;
				}
				if ( AttributeUtils.IsWddxElement ( mi ) ) {
					if ( MemberUtils.IsCollection ( mi ) ) {
						string lename = GetWddxElementOrRootElementName ( o, mi );
						IList listItems = MemberUtils.GetMemberInfoValue<IList> ( o, mi );
						foreach ( WddxObject wo in listItems ) {
							InternalSerializeElement ( w, wo, lename );
						}
					} else {
						string lename = GetWddxElementOrRootElementName ( o, mi );
						InternalSerializeElement ( w, MemberUtils.GetMemberInfoValue ( o, mi ), lename );
					}
				} else if ( AttributeUtils.IsWddxText ( mi ) ) {
					string svalue = MemberUtils.GetMemberInfoStringValue ( o, mi );
					w.WriteString ( svalue );
				}

			}
			w.WriteEndElement ( );
		}

		private string GetWddxRootElementName ( WddxObject o ) {
			WddxRootAttribute wra = AttributeUtils.GetWddxRoot ( o.GetType ( ) );
			if ( wra != null && !string.IsNullOrEmpty ( wra.ElementName ) ) {
				return wra.ElementName;
			} else {
				return o.GetType ( ).Name;
			}
		}

		private string GetWddxElementOrRootElementName ( WddxObject o, MemberInfo mi ) {
			if ( o == null ) {
				return null;
			}
			Type valType = MemberUtils.GetMemberInfoValue<object> ( o, mi ).GetType ( );
			if ( valType.IsSubclassOf ( typeof ( WddxObject ) ) ) {
				WddxElementAttribute wea = AttributeUtils.GetWddxElement ( mi );
				if ( wea != null && !string.IsNullOrEmpty ( wea.ElementName ) ) {
					return wea.ElementName;
				} else {
					WddxRootAttribute wra = AttributeUtils.GetWddxRoot ( valType );
					if ( wra != null && !string.IsNullOrEmpty ( wra.ElementName ) ) {
						return wra.ElementName;
					} else {
						return mi.Name;
					}
				}
			} else {
				WddxElementAttribute wea = AttributeUtils.GetWddxElement ( mi );
				if ( wea != null && !string.IsNullOrEmpty ( wea.ElementName ) ) {
					return wea.ElementName;
				} else {
					return null;
				}
			}
		}

		private string GetWddxAttributeName ( MemberInfo mi ) {
			WddxAttributeAttribute waa = AttributeUtils.GetWddxAttribute ( mi );
			if ( waa != null && !string.IsNullOrEmpty ( waa.AttributeName ) ) {
				return waa.AttributeName;
			} else {
				return null;
			}
		}
		#endregion

		#region Private Deserialize Methods
		private object InternalDeserializeElement ( XmlTextReader reader ) {
			if ( reader.Read ( ) ) {
				reader.MoveToElement ( );

				switch ( reader.NodeType ) {
					case XmlNodeType.Attribute:
						// handled in the element...
						break;
					case XmlNodeType.Element:
						Type t = AttributeUtils.GetTypeFromRootName ( reader.LocalName );
						if ( t != null ) {
							Console.WriteLine ( "Type Found for element: {0}:{1}", reader.LocalName, t.FullName );
							WddxObject wo = TypeUtils.CreateWddxObject ( t );
							if ( wo == null ) {
								return null;
							}
							if ( t == typeof ( WddxPacket ) ) {
								( wo as WddxPacket ).Header = InternalDeserializeElement ( reader ) as WddxHeader;
								( wo as WddxPacket ).Data = InternalDeserializeElement ( reader ) as WddxData;
							} else if ( t == typeof ( WddxHeader ) ) {
								( wo as WddxHeader ).Comment = InternalDeserializeElement ( reader ) as WddxHeaderComment;
							} else if ( t == typeof ( WddxData ) ) {
								( wo as WddxData ).Value = InternalDeserializeElement ( reader ) as WddxDataTypeObject;
							} else if ( wo.GetType ( ).IsSubclassOf ( typeof ( WddxPrimitiveDataTypeObject ) ) ) {
								if ( wo is WddxBoolean ) {
									object val = InternalDeserializeElement ( reader );
									Console.WriteLine ( "setting boolean value: {0}", val );
									( wo as WddxBoolean ).Value = string.Compare ( val != null ? val.ToString ( ) : string.Empty, bool.TrueString, true ) == 0;
								} else if ( wo is WddxString ) {
									object val = InternalDeserializeElement ( reader );
									Console.WriteLine ( "setting string value: {0}", val );
									( wo as WddxString ).Value = val != null ? val.ToString ( ) : string.Empty;
								} else if ( wo is WddxNumber ) {
									object val = InternalDeserializeElement ( reader );
									Console.WriteLine ( "setting number value: {0}", val );
									( wo as WddxNumber ).Value = Double.Parse ( val != null ? val.ToString ( ) : "0" );
								} else if ( wo is WddxNull ) {
									wo.Value = null;
								} else if ( wo is WddxBinary ) {
									object val = InternalDeserializeElement ( reader );
									Console.WriteLine ( "setting binary value: {0}", val );
									( wo as WddxBinary ).ValueString = val != null ? val.ToString ( ) : Convert.ToBase64String ( Encoding.Default.GetBytes ( string.Empty ) );
								}
							} else if ( wo.GetType ( ).IsSubclassOf ( typeof ( WddxDataTypeObject ) ) ) {
								wo.Value = InternalDeserializeElement ( reader ) as WddxDataTypeObject;
							} else {
								wo.Value = InternalDeserializeElement ( reader ) as WddxDataTypeObject;
							}
							return wo;
						} else {
							return null;
						}
					case XmlNodeType.CDATA:
					case XmlNodeType.Text:
						Console.WriteLine ( "text:{0}", reader.Value );
						return InternalDeserializeElement ( reader );
					case XmlNodeType.Whitespace:
					case XmlNodeType.XmlDeclaration:
					case XmlNodeType.Comment:
					case XmlNodeType.Document:
					case XmlNodeType.DocumentFragment:
					case XmlNodeType.DocumentType:
					case XmlNodeType.SignificantWhitespace:
					case XmlNodeType.EndEntity:
					case XmlNodeType.Entity:
					case XmlNodeType.EntityReference:
					case XmlNodeType.None:
					case XmlNodeType.Notation:
					case XmlNodeType.ProcessingInstruction:
					case XmlNodeType.EndElement:
						Console.WriteLine ( "Node Type: {0}", reader.NodeType.ToString ( ) );
						InternalDeserializeElement ( reader );
						return null;
						break;
					default:
						Console.WriteLine ( "Node Type: {0}", reader.NodeType.ToString ( ) );
						//return InternalDeserializeElement ( reader );
						break;
				}

			}
			return null;
		}
		#endregion
	}
}
